#include <d3d12_core.h>
#include <utility>
#include <array>
#include <wrl.h>
#include <d3d12_command.h>
#include <helper.h>

using DxLevel = std::pair<D3D_FEATURE_LEVEL, const wchar_t*>;

constexpr std::array<DxLevel, 11> level_mapping
{
	DxLevel{D3D_FEATURE_LEVEL_1_0_CORE,L"D3D_FEATURE_LEVEL_1_0_CORE"},
	DxLevel{D3D_FEATURE_LEVEL_9_1,L"D3D_FEATURE_LEVEL_9_1"},
	DxLevel{D3D_FEATURE_LEVEL_9_2,L"D3D_FEATURE_LEVEL_9_2"},
	DxLevel{D3D_FEATURE_LEVEL_9_3,L"D3D_FEATURE_LEVEL_9_3"},
	DxLevel{D3D_FEATURE_LEVEL_10_0,L"D3D_FEATURE_LEVEL_10_0"},
	DxLevel{D3D_FEATURE_LEVEL_10_1,L"D3D_FEATURE_LEVEL_10_1"},
	DxLevel{D3D_FEATURE_LEVEL_11_0,L"D3D_FEATURE_LEVEL_11_0"},
	DxLevel{D3D_FEATURE_LEVEL_11_1,L"D3D_FEATURE_LEVEL_11_1"},
	DxLevel{D3D_FEATURE_LEVEL_12_0,L"D3D_FEATURE_LEVEL_12_0"},
	DxLevel{D3D_FEATURE_LEVEL_12_1,L"D3D_FEATURE_LEVEL_12_1"},
	DxLevel{D3D_FEATURE_LEVEL_12_2,L"D3D_FEATURE_LEVEL_12_2"},
};

constexpr u32 DESCRIPTOR_COUNT{ 1024u };

namespace d3d12
{
	enum QUEUE_TYPE
	{
		QUEUE_TYPE_DIRECT,
		QUEUE_TYPE_COPY,
		QUEUE_TYPE_COUNT
	};

	struct DescriptorHeap
	{
		ID3D12DescriptorHeap* heap;
		UINT offset;
		u32 increment_size;
		descriptor_handle start_handle;
	};

	struct
	{
		IDXGIFactoryN* factory;
		ID3D12DeviceN* device;
		DxLevel level;
		DescriptorHeap def_heaps[D3D12_DESCRIPTOR_HEAP_TYPE_NUM_TYPES];
		ID3D12CommandQueue* cmd_queues[QUEUE_TYPE_COUNT];
		bool is_init;
	}gb{};

	bool initialize()
	{
		if (gb.is_init)
		{
			return gb.is_init;
		}

		//Begin init
		{
			UINT factory_flags{ 0u };
#ifdef _DEBUG
			{
				Microsoft::WRL::ComPtr<ID3D12Debug3> debug_interface{};
				DXASSERT(D3D12GetDebugInterface(IID_PPV_ARGS(debug_interface.GetAddressOf())));
				debug_interface->EnableDebugLayer();
			}
			factory_flags = DXGI_CREATE_FACTORY_DEBUG;
#endif // _DEBUG
			DXASSERT(CreateDXGIFactory2(factory_flags, IIDPPV(gb.factory)));

			Microsoft::WRL::ComPtr<IDXGIAdapter4> adapter;

			DXASSERT(gb.factory->EnumAdapterByGpuPreference(0u, DXGI_GPU_PREFERENCE_HIGH_PERFORMANCE, IID_PPV_ARGS(adapter.GetAddressOf())));
			
			for (auto item = level_mapping.rbegin(); item != level_mapping.rend(); item++)
			{
				if (D3D12CreateDevice(adapter.Get(), item->first, IIDPPV(gb.device)) == S_OK)
				{
					gb.level = *item;
					break;
				}
			}

			OBJ_NAME(gb.device, L"global_device");
			
#ifdef _DEBUG
			{
				Microsoft::WRL::ComPtr<ID3D12InfoQueue> info_queue{};
				gb.device->QueryInterface(info_queue.GetAddressOf());
				info_queue->SetBreakOnSeverity(D3D12_MESSAGE_SEVERITY_CORRUPTION, true);
				info_queue->SetBreakOnSeverity(D3D12_MESSAGE_SEVERITY_WARNING, true);
				info_queue->SetBreakOnSeverity(D3D12_MESSAGE_SEVERITY_ERROR, true);
				
			}
#endif // _DEBUG

			std::array<const wchar_t*, D3D12_DESCRIPTOR_HEAP_TYPE_NUM_TYPES> heap_types_str
			{
				L"SRV",
				L"SAMPLER",
				L"RTV",
				L"DSV",
			};

			for (size_t i = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV; i < D3D12_DESCRIPTOR_HEAP_TYPE_NUM_TYPES; ++i)
			{
				D3D12_DESCRIPTOR_HEAP_TYPE cur_type{ static_cast<D3D12_DESCRIPTOR_HEAP_TYPE>(i) };
				auto& cur_heap{ gb.def_heaps[i] };

				D3D12_DESCRIPTOR_HEAP_DESC heap_create_info{};
				heap_create_info.NumDescriptors = DESCRIPTOR_COUNT;
				heap_create_info.Type = cur_type;
				auto s_visibility = cur_type == D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV;
				heap_create_info.Flags = s_visibility ? D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE : D3D12_DESCRIPTOR_HEAP_FLAG_NONE;

				DXASSERT(gb.device->CreateDescriptorHeap(&heap_create_info, IIDPPV(cur_heap.heap)));

				OBJ_NAME(cur_heap.heap, L"%s_heap", heap_types_str[i]);

				cur_heap.start_handle.cpu = cur_heap.heap->GetCPUDescriptorHandleForHeapStart();
				if (s_visibility)
				{
					cur_heap.start_handle.gpu = cur_heap.heap->GetGPUDescriptorHandleForHeapStart();
				}

				cur_heap.increment_size = gb.device->GetDescriptorHandleIncrementSize(cur_type);
			}

			{
				auto& cmd{ gb.cmd_queues[QUEUE_TYPE_DIRECT] };
				D3D12_COMMAND_QUEUE_DESC queue_create_info{};
				queue_create_info.Type = D3D12_COMMAND_LIST_TYPE_DIRECT;
				DXASSERT(gb.device->CreateCommandQueue(&queue_create_info, IIDPPV(cmd)));
				OBJ_NAME(cmd, L"DIRECT_Queue");
			}
			{
				auto& cmd{ gb.cmd_queues[QUEUE_TYPE_COPY] };
				D3D12_COMMAND_QUEUE_DESC queue_create_info{};
				queue_create_info.Type = D3D12_COMMAND_LIST_TYPE_COPY;
				DXASSERT(gb.device->CreateCommandQueue(&queue_create_info, IIDPPV(cmd)));
				OBJ_NAME(cmd, L"COPY_Queue");
			}
		}

		gb.is_init = true;
		return gb.is_init;
	}

	void shutdown()
	{
		for (size_t i = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV; i < D3D12_DESCRIPTOR_HEAP_TYPE_NUM_TYPES; ++i)
		{
			auto& cur_heap{ gb.def_heaps[i] };
			release(cur_heap.heap);
		}
		release(gb.cmd_queues[QUEUE_TYPE_DIRECT]);
		release(gb.cmd_queues[QUEUE_TYPE_COPY]);
		release(gb.factory);

#ifdef _DEBUG
		{
			Microsoft::WRL::ComPtr<ID3D12InfoQueue> info_queue{};
			gb.device->QueryInterface(IID_PPV_ARGS(info_queue.GetAddressOf()));
			info_queue->SetBreakOnSeverity(D3D12_MESSAGE_SEVERITY_CORRUPTION, false);
			info_queue->SetBreakOnSeverity(D3D12_MESSAGE_SEVERITY_WARNING, false);
			info_queue->SetBreakOnSeverity(D3D12_MESSAGE_SEVERITY_ERROR, false);
		}
		{
			constexpr D3D12_RLDO_FLAGS fg = D3D12_RLDO_DETAIL | D3D12_RLDO_SUMMARY | D3D12_RLDO_IGNORE_INTERNAL;
			Microsoft::WRL::ComPtr<ID3D12DebugDevice2> debug_device;
			DXASSERT(gb.device->QueryInterface(IID_PPV_ARGS(debug_device.GetAddressOf())));
			release(gb.device);
			DXASSERT(debug_device->ReportLiveDeviceObjects(fg));
		}
#endif // _DEBUG

		release(gb.device);
	}

	const wchar_t* get_level_name()
	{
		assert(gb.is_init);
		return gb.level.second;
	}

	ID3D12DeviceN* device()
	{
		assert(gb.is_init);
		return gb.device;
	}

	ID3D12DescriptorHeap* get_desc_heap(D3D12_DESCRIPTOR_HEAP_TYPE idx)
	{
		assert(gb.is_init);
		assert(idx >= D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV && idx < D3D12_DESCRIPTOR_HEAP_TYPE_NUM_TYPES);
		return gb.def_heaps[(u32)idx].heap;
	}

	descriptor_handle allocator_handle(D3D12_DESCRIPTOR_HEAP_TYPE idx)
	{
		assert(gb.is_init);
		auto& heap{ gb.def_heaps[idx] };
		auto handle = heap.start_handle;

		auto offset{ heap.offset * heap.increment_size };
		++heap.offset;

		handle.cpu.ptr += offset;
		handle.gpu.ptr += offset;

		return handle;
	}

	ID3D12CommandQueue* get_direct_queue()
	{
		assert(gb.is_init);
		return gb.cmd_queues[QUEUE_TYPE_DIRECT];
	}

	ID3D12CommandQueue* get_copy_queue()
	{
		assert(gb.is_init);
		return gb.cmd_queues[QUEUE_TYPE_COPY];
	}

	bool d3d12::Command::SurfaceCreate(Surface& sur, HWND winid)
	{
		GetClientRect(winid, &sur.rectangle);
		d3dx::swapchain_desc swapchain_create_info{};
		swapchain_create_info.init_unvsync(0u, 0u, COMMAND_FRAME_COUNT);
		
		Microsoft::WRL::ComPtr<IDXGISwapChain1> temp_swapchain;

		DXASSERT(gb.factory->CreateSwapChainForHwnd
		(
			gb.cmd_queues[QUEUE_TYPE_DIRECT],
			winid,
			&swapchain_create_info,
			nullptr,
			nullptr,
			temp_swapchain.GetAddressOf()
		));

		DXASSERT(temp_swapchain->QueryInterface(IID_PPV_ARGS(&sur.swapchain)));

		for (auto& item : sur.frames)
		{
			item.rt_handle = allocator_handle(D3D12_DESCRIPTOR_HEAP_TYPE_RTV);
			item.ds_handle = allocator_handle(D3D12_DESCRIPTOR_HEAP_TYPE_DSV);
		}

		sur.flag = DXGI_PRESENT_ALLOW_TEARING;
		sur.sync_interval = 0u;
		sur.parameters = {};

#ifdef _DEBUG
		sur.createor = this;
#endif // _DEBUG

		SurfaceResize(sur, sur.rectangle.right, sur.rectangle.bottom);

		return true;
	}

}